Skip to content

feat: add support openai/privacy-filter model#1104

Merged
NorbertKlockiewicz merged 5 commits into
mainfrom
@nk/privacy-filter
Apr 30, 2026
Merged

feat: add support openai/privacy-filter model#1104
NorbertKlockiewicz merged 5 commits into
mainfrom
@nk/privacy-filter

Conversation

@NorbertKlockiewicz
Copy link
Copy Markdown
Contributor

@NorbertKlockiewicz NorbertKlockiewicz commented Apr 27, 2026

Description

Adds a new Privacy Filter native model for on-device PII detection, with a TypeScript hook (usePrivacyFilter), module (PrivacyFilterModule), types, and a demo screen in the apps/llm example. Two models are wired up out-of-the-box:

  • PRIVACY_FILTER_OPENAIopenai/privacy-filter, 8 categories (person, email, phone, address, DOB, URL, account number, secret).
  • PRIVACY_FILTER_NEMOTRONOpenMed/privacy-filter-nemotron-base, 55+ categories adding medical, financial, demographic, technical, and government-document spans.

Both models share the same single-method forward(input_ids, attention_mask) → logits graph, the same o200k tokenizer (pad/eos id 199999), and 256-token banded attention. They differ only in the BIOES label space, which is supplied as labelNames at load time.

Highlights:

  • Constrained Viterbi decoding with six tunable BIOES transition biases, matching OpenAI's viterbi_calibration.json schema. Defaults to neutral validity-only Viterbi.
  • Sliding-window inference: 256-token windows, 50% overlap, no truncation — arbitrary-length input is supported.
  • seq_len is read from the model's forward input shape at load time, so a 512-token export would Just Work without code changes.
  • Typed JSI return: generate() returns PiiEntity[] directly via a getJsiValue overload — no JSON bridge.
  • Viterbi decoder is split into its own Viterbi.{h,cpp} module under rnexecutorch::models::privacy_filter::viterbi, mirroring the Utils.{h,cpp} convention used by VAD.

Introduces a breaking change?

  • Yes
  • No

Type of change

  • Bug fix (change which fixes an issue)
  • New feature (change which adds functionality)
  • Documentation update (improves or adds clarity to existing documentation)
  • Other (chores, tests, code style improvements etc.)

Tested on

  • iOS
  • Android

Testing instructions

  1. cd apps/llm && yarn install
  2. iOS: yarn pods && yarn ios · Android: yarn android
  3. From the home screen, open "Privacy Filter".
  4. Pick a model in the picker (OpenAI base or Nemotron fine-tune) — the model downloads on first run.
  5. Tap "Run" to scan the bundled sample text.
  6. Verify detected entity spans are highlighted inline with their label and color, and inference time is shown on the button.
  7. Switch models and re-run — entity coverage should differ between the two (Nemotron picks up medical/financial/demographic categories the base model doesn't have).

Screenshots

Related issues

Checklist

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings

Additional notes

  • The tokenizer.json is bit-identical between the OpenAI base and the Nemotron fine-tune; both use the GPT-4o o200k BPE.
  • All PTE files are exported as a single forward method rather than the 18-method multimethod export — this brings RAM down from ~3.5 GB to ~1.2 GB on iPhone 16 Pro and inference from ~8s to ~1.5s, since planner buffers are reused across the full graph.
  • Docs are added to docs/docs/ (next-version only). The 06-api-reference/ typedoc output regenerates automatically from the @category Models - Privacy Filter tags on the exported constants and the public hook/module/types — no manual entries were added there.
  • Benchmarks (02-benchmarks/) are intentionally not updated in this PR; we'll add measured numbers in a follow-up once we have proper multi-device timings.

@NorbertKlockiewicz NorbertKlockiewicz self-assigned this Apr 27, 2026
@NorbertKlockiewicz NorbertKlockiewicz added the model Issues related to exporting, improving, fixing ML models label Apr 27, 2026
@NorbertKlockiewicz NorbertKlockiewicz linked an issue Apr 27, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Collaborator

@chmjkb chmjkb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure itf its good time to review this as this is a draft 😅
but I only noticed after starting the review so heres a couple of omments

@NorbertKlockiewicz NorbertKlockiewicz force-pushed the @nk/privacy-filter branch 2 times, most recently from 01b99fe to a0e04b4 Compare April 30, 2026 09:43
NorbertKlockiewicz and others added 2 commits April 30, 2026 11:50
Adds a new PrivacyFilter native model with TS hook, module, types and
demo screen, plus the openai/privacy-filter and OpenMed/privacy-filter
nemotron presets. Uses constrained Viterbi decoding with tunable BIOES
transition biases (matching openai's viterbi_calibration.json schema),
sliding 256-token windows with 50% overlap for arbitrary-length input,
and exposes results as a typed PiiEntity[] via a getJsiValue overload.
seq_len is read from the forward method's input shape so the same
runner works with any single-method privacy-filter export. Viterbi
decoder is split into its own module (Viterbi.{h,cpp}) following the
peer Utils.{h,cpp} convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NorbertKlockiewicz NorbertKlockiewicz marked this pull request as ready for review April 30, 2026 10:00
Comment thread apps/llm/app.json Outdated
@msluszniak
Copy link
Copy Markdown
Member

FYI, I tested demo apps and they worked correctly :))

- Strip personal appleTeamId from apps/llm/app.json
- Use std::cmp_greater_equal for the labelId bounds check
- Take padded buffers by non-const ref in runWindow so make_tensor_ptr
  no longer needs const_cast on the data pointers
- Move the validLen <= 0 early-return above the forward call so we
  don't run inference on empty windows
- Replace the trusted-slice write loop with std::copy
- Drop redundant parens around isFirst / isLast
- emplace_back the merged spans
- Drop the redundant "decoded = """ inside the catch block (decoded
  is already default-constructed empty)
- Use std::ranges::find_if + reverse iterator for the trim block,
  emplace_back the resulting PiiEntity
- Use std::ranges::max_element for the Viterbi best-end search and
  drop the now-unused bestScore variable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@msluszniak msluszniak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass — a few correctness issues + polish.

Comment thread packages/react-native-executorch/src/types/privacyFilter.ts Outdated
Comment thread apps/llm/utils/piiMatching.ts
Comment thread apps/llm/app/privacy_filter/index.tsx
- Throw on wrong-sized viterbiBiases (size != 0 && size != 6) instead
  of silently falling through to defaults
- Validate labelNames_[0] == "O" to match what the error message claims
- Reject seq_len < 2 in the constructor (would otherwise make the
  sliding-window stride 0 and loop forever)
- Comment Viterbi bp's unused first row (row 0 is allocated for index
  convenience; traceback starts at t = 1)
- Drop hardcoded 256/128 magic numbers from the public JSDoc and the
  hook/module docs — restate as "50% overlap, window from forward
  input shape" so a future seq_len change doesn't drift the docs
- Word-boundary-anchored regex in piiMatching to avoid "John" matching
  inside "Johnson"
- Simplify the demo's run-error handler to the one-liner the hook's
  RnExecutorchError already supports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@msluszniak msluszniak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :))

@NorbertKlockiewicz NorbertKlockiewicz merged commit 33b2fab into main Apr 30, 2026
5 checks passed
@NorbertKlockiewicz NorbertKlockiewicz deleted the @nk/privacy-filter branch April 30, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model Issues related to exporting, improving, fixing ML models

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding support for OpenAI/Privacy-Filter

3 participants